// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/socket/client_socket_handle.h"

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_pool.h"

namespace net {

ClientSocketHandle::ClientSocketHandle()
    : is_initialized_(false)
    , pool_(NULL)
    , higher_pool_(NULL)
    , reuse_type_(ClientSocketHandle::UNUSED)
    , callback_(base::Bind(&ClientSocketHandle::OnIOComplete,
          base::Unretained(this)))
    , is_ssl_error_(false)
{
}

ClientSocketHandle::~ClientSocketHandle()
{
    Reset();
}

void ClientSocketHandle::Reset()
{
    ResetInternal(true);
    ResetErrorState();
}

void ClientSocketHandle::ResetInternal(bool cancel)
{
    // Was Init called?
    if (!group_name_.empty()) {
        // If so, we must have a pool.
        CHECK(pool_);
        if (is_initialized()) {
            if (socket_) {
                socket_->NetLog().EndEvent(NetLog::TYPE_SOCKET_IN_USE);
                // Release the socket back to the ClientSocketPool so it can be
                // deleted or reused.
                pool_->ReleaseSocket(group_name_, std::move(socket_), pool_id_);
            } else {
                // If the handle has been initialized, we should still have a
                // socket.
                NOTREACHED();
            }
        } else if (cancel) {
            // If we did not get initialized yet and we have a socket
            // request pending, cancel it.
            pool_->CancelRequest(group_name_, this);
        }
    }
    is_initialized_ = false;
    socket_.reset();
    group_name_.clear();
    reuse_type_ = ClientSocketHandle::UNUSED;
    user_callback_.Reset();
    if (higher_pool_)
        RemoveHigherLayeredPool(higher_pool_);
    pool_ = NULL;
    idle_time_ = base::TimeDelta();
    init_time_ = base::TimeTicks();
    setup_time_ = base::TimeDelta();
    connect_timing_ = LoadTimingInfo::ConnectTiming();
    pool_id_ = -1;
}

void ClientSocketHandle::ResetErrorState()
{
    is_ssl_error_ = false;
    ssl_error_response_info_ = HttpResponseInfo();
    pending_http_proxy_connection_.reset();
}

LoadState ClientSocketHandle::GetLoadState() const
{
    CHECK(!is_initialized());
    CHECK(!group_name_.empty());
    // Because of http://crbug.com/37810  we may not have a pool, but have
    // just a raw socket.
    if (!pool_)
        return LOAD_STATE_IDLE;
    return pool_->GetLoadState(group_name_, this);
}

bool ClientSocketHandle::IsPoolStalled() const
{
    if (!pool_)
        return false;
    return pool_->IsStalled();
}

void ClientSocketHandle::AddHigherLayeredPool(HigherLayeredPool* higher_pool)
{
    CHECK(higher_pool);
    CHECK(!higher_pool_);
    // TODO(mmenke):  |pool_| should only be NULL in tests.  Maybe stop doing that
    // so this be be made into a DCHECK, and the same can be done in
    // RemoveHigherLayeredPool?
    if (pool_) {
        pool_->AddHigherLayeredPool(higher_pool);
        higher_pool_ = higher_pool;
    }
}

void ClientSocketHandle::RemoveHigherLayeredPool(
    HigherLayeredPool* higher_pool)
{
    CHECK(higher_pool_);
    CHECK_EQ(higher_pool_, higher_pool);
    if (pool_) {
        pool_->RemoveHigherLayeredPool(higher_pool);
        higher_pool_ = NULL;
    }
}

bool ClientSocketHandle::GetLoadTimingInfo(
    bool is_reused,
    LoadTimingInfo* load_timing_info) const
{
    // Only return load timing information when there's a socket.
    if (!socket_)
        return false;

    load_timing_info->socket_log_id = socket_->NetLog().source().id;
    load_timing_info->socket_reused = is_reused;

    // No times if the socket is reused.
    if (is_reused)
        return true;

    load_timing_info->connect_timing = connect_timing_;
    return true;
}

void ClientSocketHandle::SetSocket(std::unique_ptr<StreamSocket> s)
{
    socket_ = std::move(s);
}

void ClientSocketHandle::OnIOComplete(int result)
{
    TRACE_EVENT0("net", "ClientSocketHandle::OnIOComplete");
    CompletionCallback callback = user_callback_;
    user_callback_.Reset();
    HandleInitCompletion(result);
    callback.Run(result);
}

std::unique_ptr<StreamSocket> ClientSocketHandle::PassSocket()
{
    return std::move(socket_);
}

void ClientSocketHandle::HandleInitCompletion(int result)
{
    CHECK_NE(ERR_IO_PENDING, result);
    if (result != OK) {
        if (!socket_.get())
            ResetInternal(false); // Nothing to cancel since the request failed.
        else
            is_initialized_ = true;
        return;
    }
    is_initialized_ = true;
    CHECK_NE(-1, pool_id_) << "Pool should have set |pool_id_| to a valid value.";
    setup_time_ = base::TimeTicks::Now() - init_time_;

    // Broadcast that the socket has been acquired.
    // TODO(eroman): This logging is not complete, in particular set_socket() and
    // release() socket. It ends up working though, since those methods are being
    // used to layer sockets (and the destination sources are the same).
    DCHECK(socket_.get());
    socket_->NetLog().BeginEvent(
        NetLog::TYPE_SOCKET_IN_USE,
        requesting_source_.ToEventParametersCallback());
}

} // namespace net
